import { EnvPathType, EQEnvInfo, EQEnvKind, EQEnvSource, EQReleaseLevel, EQVersion, globallyInstalledEnvKinds, virtualEnvKinds } from ".";
import { arePathsSame, isParentPath, normCasePath } from "../../common/externalDependencies";
import { BasicEnvInfo } from "../locator";
import { areIdenticalVersion, areSimilarVersions, getVersionDisplayString, isVersionEmpty } from "./eqVersion";
import * as path from 'path';
import { getArchitectureDisplayName } from "../../../common/platform/registry";
import { getKindDisplayName } from "./envKind";
import { Architecture } from "../../../common/utils/platform";
import { Uri } from "vscode";
function getMinimalPartialInfo(env: string | EQEnvInfo | BasicEnvInfo): Partial<EQEnvInfo> | undefined {
    if (typeof env === 'string') {
        if (env === '') {
            return undefined;
        }
        return {
            executable: {
                filename: env,
                sysPrefix: '',
                ctime: -1,
                mtime: -1,
            },
        };
    }
    if ('executablePath' in env) {
        return {
            executable: {
                filename: env.executablePath,
                sysPrefix: '',
                ctime: -1,
                mtime: -1,
            },
            location: env.envPath,
            kind: env.kind,
            source: env.source,
        };
    }
    return env;
}

export function getEnvPath(interpreterPath: string, envFolderPath?: string): EnvPathType {
    let envPath: EnvPathType = { path: interpreterPath, pathType: 'interpreterPath' };
    if (envFolderPath && !isParentPath(interpreterPath, envFolderPath)) {
        // Executable is not inside the environment folder, env folder is the ID.
        envPath = { path: envFolderPath, pathType: 'envFolderPath' };
    }
    return envPath;
}

export function getEnvID(interpreterPath: string, envFolderPath?: string): string {
    return normCasePath(getEnvPath(interpreterPath, envFolderPath).path);
}

export function areSameEnv(
    left: string | EQEnvInfo | BasicEnvInfo,
    right: string | EQEnvInfo | BasicEnvInfo,
    allowPartialMatch = true,
): boolean | undefined {
    const leftInfo = getMinimalPartialInfo(left);
    const rightInfo = getMinimalPartialInfo(right);
    if (leftInfo === undefined || rightInfo === undefined) {
        return undefined;
    }
    const leftFilename = leftInfo.executable!.filename;
    const rightFilename = rightInfo.executable!.filename;

    if (getEnvID(leftFilename, leftInfo.location) === getEnvID(rightFilename, rightInfo.location)) {
        return true;
    }

    if (allowPartialMatch && arePathsSame(path.dirname(leftFilename), path.dirname(rightFilename))) {
        const leftVersion = typeof left === 'string' ? undefined : leftInfo.version;
        const rightVersion = typeof right === 'string' ? undefined : rightInfo.version;
        if (leftVersion && rightVersion) {
            if (areIdenticalVersion(leftVersion, rightVersion) || areSimilarVersions(leftVersion, rightVersion)) {
                return true;
            }
        }
    }
    return false;
}


function buildEnvDisplayString(env: EQEnvInfo, getAllDetails = false): string {
    // main parts
    const shouldDisplayKind = getAllDetails || env.searchLocation || globallyInstalledEnvKinds.includes(env.kind);
    const shouldDisplayArch = !virtualEnvKinds.includes(env.kind);
    const displayNameParts: string[] = ['EQ'];
    if (env.version && !isVersionEmpty(env.version)) {
        displayNameParts.push(getVersionDisplayString(env.version));
    }
    if (shouldDisplayArch) {
        const archName = getArchitectureDisplayName(env.arch);
        if (archName !== '') {
            displayNameParts.push(archName);
        }
    }

    // Note that currently we do not use env.distro in the display name.

    // "suffix"
    const envSuffixParts: string[] = [];
    if (env.name && env.name !== '') {
        envSuffixParts.push(`'${env.name}'`);
    }
    if (shouldDisplayKind) {
        const kindName = getKindDisplayName(env.kind);
        if (kindName !== '') {
            envSuffixParts.push(kindName);
        }
    }
    const envSuffix = envSuffixParts.length === 0 ? '' : `(${envSuffixParts.join(': ')})`;

    // Pull it all together.
    return `${displayNameParts.join(' ')} ${envSuffix}`.trim();
}

export function setEnvDisplayString(env: EQEnvInfo): void {
    env.display = buildEnvDisplayString(env);
    env.detailedDisplayName = buildEnvDisplayString(env, true);
}

export function compareEQVersionSpecificity(versionA: EQVersion, versionB: EQVersion): number {
    return Math.sign(getEQVersionSpecificity(versionA) - getEQVersionSpecificity(versionB));
}
function getEQVersionSpecificity(version: EQVersion): number {
    let infoLevel = 0;
    if (version.major > 0) {
        infoLevel += 20; // W4
    }

    if (version.minor >= 0) {
        infoLevel += 10; // W3
    }

    if (version.micro >= 0) {
        infoLevel += 5; // W2
    }

    if (version.release?.level) {
        infoLevel += 3; // W1
    }

    if (version.release?.serial || version.sysVersion) {
        infoLevel += 1; // W0
    }

    return infoLevel;
}

export function buildEnvInfo(init?: {
    kind?: EQEnvKind;
    executable?: string;
    name?: string;
    location?: string;
    version?: EQVersion;
    org?: string;
    arch?: Architecture;
    fileInfo?: { ctime: number; mtime: number };
    source?: EQEnvSource[];
    display?: string;
    sysPrefix?: string;
    searchLocation?: Uri;
}): EQEnvInfo {
    const env: EQEnvInfo = {
        name: init?.name ?? '',
        location: '',
        kind: EQEnvKind.Unknown,
        executable: {
            filename: '',
            sysPrefix: init?.sysPrefix ?? '',
            ctime: init?.fileInfo?.ctime ?? -1,
            mtime: init?.fileInfo?.mtime ?? -1,
        },
        searchLocation: undefined,
        display: init?.display,
        version: {
            major: -1,
            minor: -1,
            micro: -1,
            release: {
                level: EQReleaseLevel.Final,
                serial: 0,
            },
        },
        arch: init?.arch ?? Architecture.Unknown,
        distro: {
            org: init?.org ?? '',
        },
        source: init?.source ?? [],
    };
    if (init !== undefined) {
        updateEnv(env, init);
    }
    env.id = getEnvID(env.executable.filename, env.location);
    return env;
}

function updateEnv(
    env: EQEnvInfo,
    updates: {
        kind?: EQEnvKind;
        executable?: string;
        location?: string;
        version?: EQVersion;
        searchLocation?: Uri;
    },
): void {
    if (updates.kind !== undefined) {
        env.kind = updates.kind;
    }
    if (updates.executable !== undefined) {
        env.executable.filename = updates.executable;
    }
    if (updates.location !== undefined) {
        env.location = updates.location;
    }
    if (updates.version !== undefined) {
        env.version = updates.version;
    }
    if (updates.searchLocation !== undefined) {
        env.searchLocation = updates.searchLocation;
    }
}
